use crate::skill::Skill;
use std::{
    io::{stdin, stdout, Write},
    marker::PhantomData,
};

pub struct Game<L, R, S> {
    l_player: L,
    l_ji: usize,
    r_player: R,
    r_ji: usize,
    log: bool,
    _marker: PhantomData<S>,
}

pub trait Player<S> {
    fn play(&mut self, my: usize, their: usize) -> S;
}

macro_rules! log {
    ($cond: expr, $($arg:tt)*) => {
        if $cond {
            println!($($arg)*);
        }
    };
}

impl<L, R, S> Game<L, R, S> {
    pub fn new(l: L, r: R, log: bool) -> Self {
        log!(log, "| 游戏开始！");
        Game {
            l_player: l,
            l_ji: 0,
            r_player: r,
            r_ji: 0,
            log,
            _marker: PhantomData,
        }
    }
}

impl<L, R, S> Game<L, R, S>
where
    L: Player<S>,
    R: Player<S>,
    S: Skill,
{
    pub fn round(&mut self) -> Option<bool> {
        let l = self.l_player.play(self.l_ji, self.r_ji);
        let r = self.r_player.play(self.r_ji, self.l_ji);
        log!(self.log, "| 左边出了{l:?}，右边出了{r:?}。");

        let take = |ji: usize, s: S| ji.checked_add_signed((-s.cost()).into()).expect("集不够");
        self.l_ji = take(self.l_ji, l);
        self.r_ji = take(self.r_ji, r);
        log!(self.log, "| 左边还有{}，右边还有{}。", self.l_ji, self.r_ji);

        let result = l.versus(r);
        if let Some(left) = result {
            let winner = if left { "左边" } else { "右边" };
            log!(self.log, "| {winner}胜利了，游戏结束！");
        }
        result
    }
}

pub struct Human;

impl<S> Player<S> for Human
where
    S: Skill,
{
    fn play(&mut self, my: usize, _their: usize) -> S {
        loop {
            println!("- 请输入要出的序号");
            for (i, s) in S::all_enough(my).enumerate() {
                println!("- {}: {s:?} ({:+})", i, -s.cost());
            }
            print!("> ");
            stdout().flush().unwrap();

            let mut buf = String::new();
            stdin().read_line(&mut buf).unwrap();
            let Ok(input) = buf.trim().parse() else {
                continue;
            };
            let Some(s) = S::from_index(input) else {
                continue;
            };
            if s.enough(my) {
                break s;
            }
        }
    }
}
